// 简化版的
function clone (obj) {
    if (obj === null || typeof obj !== 'object') {
        return obj
    }

    if (obj instanceof Array) {
        return obj.reduce((arr, item, i) => {
            arr[i] = clone(item);
            return arr
        }, [])
    }

    if (obj instanceof Object) {
        return Object.keys(obj).reduce((newObj, key) => {
            newObj[key] = clone(obj[key])
            return newObj
        }, {})
    }
}

export function deepClone (source) {
    let target = createObject(source)
    let map = new Map()
    map.set(source, target)
    let stack = [{ target: target, source: source }]
    while (stack.length) {
        let { target, source } = stack.pop()
        for (let property of Object.getOwnPropertyNames(source)) {
            let descriptor = Object.getOwnPropertyDescriptor(source, property)
            if (descriptor.hasOwnProperty('value')) {
                if (typeof source[property] === 'function' || typeof source[property] === 'object' && source[property] !== null) {
                    if (map.has(source[property])) {
                        descriptor.value = map.get(source[property])
                    } else {
                        descriptor.value = createObject(source[property])
                        map.set(source[property], descriptor.value)
                        stack.push({ target: descriptor.value, source: source[property] })
                    }
                } else {
                    descriptor.value = source[property]
                }
                Object.defineProperty(target, property, descriptor)
            } else {
                Object.defineProperty(target, property, descriptor)
            }
        }
        if (Object.isFrozen(source)) {
            Object.freeze(target)
        } else if (Object.isSealed(source)) {
            Object.seal(target)
        } else if (!Object.isExtensible(source)) {
            Object.preventExtensions(target)
        }
    }
    return target
}

let classes = [
    {
        check (source, type, toStringResult, prototype) {
            return prototype === Array.prototype && toStringResult === '[object Array]'
        },
        create (source) {
            return source.slice()
        }
    },
    {
        check (source, type, toStringResult, prototype) {
            return prototype === String.prototype && toStringResult === '[object String]'
        },
        create (source) {
            return new String(source.valueOf())
        }
    },
    {
        check (source, type, toStringResult, prototype) {
            return type === 'function'
        },
        create (source) {
            return function (...args) {
                return source.call(this, ...args)
            }
        }
    },
]

function createObject (source) {
    let type = typeof source
    let toStringResult = Object.prototype.toString.call(source)
    let prototype = Object.getPrototypeOf(source)

    for (let { check, create } of classes) {
        if (check(source, type, toStringResult, prototype)) {
            return create(source)
        }
    }
    return Object.create(Object.getPrototypeOf(source))
}

export function registerClass (check, create) {
    classes.push({ check, create })
}